﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Globalization;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;
using System.Windows;
using System.Windows.Data;
using System.Windows.Markup;


namespace GaleSong
{
    /// <summary>
    ///     Custom <see cref="MarkupExtension" /> used to provide string,
    ///     it internally use <see cref="IResourceService" /> to query the string source from name key.
    /// </summary>
    [MarkupExtensionReturnType(typeof(BindingExpression))]
    public class StringBindingExtension : StringResourceExtension
    {
        private readonly FrameworkElement element = new FrameworkElement();

        public StringBindingExtension(string path)
            : this()
        {
            Path = path;
        }

        public StringBindingExtension()
        {
            DependencyPropertyDescriptor dpd = DependencyPropertyDescriptor.FromProperty(FrameworkElement.TagProperty,
                typeof(FrameworkElement));
            dpd.AddValueChanged(element, (s, e) =>
            {
                Key = element.Tag != null ? element.Tag.ToString() : null;
                NotifyValueChanged();
            });
        }

        [ConstructorArgument("path")]
        public string Path { get; set; }

        public override object ProvideValue(IServiceProvider serviceProvider)
        {
            var target = serviceProvider.GetService(typeof(IProvideValueTarget)) as IProvideValueTarget;

            if(target.TargetObject is DependencyObject)
            {
                var bind = new Binding(string.Format("DataContext.{0}", Path));
                bind.Source = target.TargetObject;
                element.SetBinding(FrameworkElement.TagProperty, bind);

                var binding = new Binding("Value") { Source = this, Mode = BindingMode.OneWay };
                return binding.ProvideValue(serviceProvider);
            }
            return this;
        }
    }

    public sealed class DesignUICultureAttribute : Attribute
    {
        public DesignUICultureAttribute(string culture)
        {
            Culture = culture;
        }

        public string Culture { get; set; }
    }

    /// <summary>
    ///     Custom <see cref="MarkupExtension" /> used to provide string,
    ///     it internally use <see cref="IResourceService" /> to query the string source from name key.
    /// </summary>
    [MarkupExtensionReturnType(typeof(BindingExpression))]
    public class StringResourceExtension : MarkupExtension, INotifyPropertyChanged
    {
        private static IList<IResource> designResourceList = new List<IResource>();

        public StringResourceExtension(string key)
            : this()
        {
            Key = key;
        }

        public StringResourceExtension()
        {
            if(!UICommon.IsInDesignMode)
            {
                Messengers.ResourceMessenger.Register<LanguageChangedMessage>(this, UpdateLocalizationString);
            }
        }

        [ConstructorArgument("key")]
        public string Key { get; set; }

        public string StringFormat { get; set; }

        public string Value
        {
            get
            {
                if(Key == null)
                {
                    return null;
                }

                try
                {
                    string result = IoC.Get<IResourceService>().GetString(Key);
                    if(StringFormat != null)
                    {
                        return string.Format(StringFormat, result);
                    }
                    return result;
                }
                catch(Exception)
                {
                    return null;
                }
            }
        }

        public static IList<IResource> GetResourceAssembies()
        {
            IList<IResource> resources = new List<IResource>();
            // Should work at runtime
            IEnumerable<Assembly> assemblies =
                AppDomain.CurrentDomain.GetAssemblies().Where(a => a.ContainsAtrribute<DesignUICultureAttribute>());
            foreach(Assembly assembly in assemblies)
            {
                foreach(Type type in assembly.GetTypes())
                {
                    if(type.GetInterface(typeof(IResource).FullName) != null)
                    {
                        var resource = (IResource)Activator.CreateInstance(type);

                        var attribute = assembly.GetAtrribute<DesignUICultureAttribute>();
                        resource.CurrentCulture = new CultureInfo(attribute.Culture);

                        resources.Add(resource);
                        break;
                    }
                }
            }

            return resources;
        }

        public override object ProvideValue(IServiceProvider serviceProvider)
        {
            var target = serviceProvider.GetService(typeof(IProvideValueTarget)) as IProvideValueTarget;

            if(UICommon.IsInDesignMode)
            {
                if(!designResourceList.Any())
                {
                    designResourceList = GetResourceAssembies();
                }

                if(designResourceList.Any())
                {
                    foreach(IResource resource in designResourceList)
                    {
                        string value = resource.GetString(Key);
                        if(value != null)
                        {
                            return value;
                        }
                    }
                }
                return "NotFound";
            }

            var setter = target.TargetObject as Setter;
            if(setter != null)
            {
                return SetBinding();
            }
            var binding = new Binding("Value") { Source = this, Mode = BindingMode.OneWay };
            return binding.ProvideValue(serviceProvider);
        }

        public Binding SetBinding()
        {
            return new Binding("Value") { Source = this, Mode = BindingMode.OneWay };
        }

        #region INotifyPropertyChanged

        private static readonly PropertyChangedEventArgs ValueChangedEventArgs = new PropertyChangedEventArgs("Value");
        public event PropertyChangedEventHandler PropertyChanged;

        protected void NotifyValueChanged()
        {
            if(PropertyChanged != null)
                PropertyChanged(this, ValueChangedEventArgs);
        }

        #endregion

        #region LanguageChangedMessage

        public void UpdateLocalizationString(LanguageChangedMessage message)
        {
            NotifyValueChanged();
        }

        #endregion
    }

    public class WeakStringBinding
    {
        private readonly WeakReference _source;
        private readonly string _sourceProperty;
        private string _key;

        public WeakStringBinding(object source, string sourceProperty, string key, IStringFormater stringFormater)
        {
            _source = new WeakReference(source);
            _sourceProperty = sourceProperty;
            _key = key;
            StringFormat = stringFormater;

            UpdateText();

            Messengers.ResourceMessenger.Register<LanguageChangedMessage>(this, UpdateLocalizationString);
        }

        public IStringFormater StringFormat { get; set; }

        private void UpdateText()
        {
            string text;
            try
            {
                text = IoC.Get<IResourceService>().GetString(_key);
                if(StringFormat != null)
                {
                    text = StringFormat.Format(text);
                }
            }
            catch(Exception)
            {
                text = "Can't find " + _key;
            }
            EmitEngine.SetProperty(_source.Target, _sourceProperty, text);
        }

        public void UpdateKey(string key)
        {
            _key = key;
            UpdateText();
        }

        #region IHandle<LanguageChangedMessage> Members

        public void UpdateLocalizationString(LanguageChangedMessage message)
        {
            if (!_source.IsAlive)
            {
                return;
            }

            UpdateText();
        }

        #endregion
    }

    public class WeakStringBinding<TSource> : WeakStringBinding
    {
        public WeakStringBinding(TSource source, Expression<Func<TSource, object>> sourceProperty, string key,
            IStringFormater stringFormater)
            : base(source, sourceProperty.GetName(), key, stringFormater)
        {
        }
    }
}